#!/usr/bin/env python
# -*- coding:utf-8 -*-


'''
@CreateTime: 2020/11/05 9:57
@Author : shouke
'''

import re
import os
import os.path
import logging
import importlib
import types


logger = logging.getLogger(__name__)

PUGIN_FUNC_MAP = {} # 存放加载的插件函数
REG_FOR_ARRAY_ELEMENT = re.compile('(.+?)\[\d+\]', re.DOTALL) # 用于查找数组元素
REG_FOR_VAR_EXPRESS = re.compile('\${[^_]+?}', re.DOTALL)  # 用于查找变量表达式
REG_FOR_TEMP_PLUGIN_FUNC = re.compile('@(plugin_func.+?)@', re.DOTALL) # 用于查找临时插件函数名称 形如 [''@plugin_func__base64@','@plugin_func__read_file@']
REG_FOR_FUNC_NAME_OF_EXP  = re.compile('\${\s*(_.+?)\(', re.DOTALL) # 用于查找函数表达式中的函数名称
REG_FOR_FUNC_NAME_AND_ARGS = re.compile('\${\s*(_.+?)\((.*?)\)\s*}', re.DOTALL) # 用于查找函数表达式中的函数定义（函数名称及其参数）
REG_FOR_STRICT_FUNC_EXP = re.compile('\${\s*_.+\(.*?\)\s*}', re.DOTALL) # 用于获取严格函数定义表达式
REG_FOR_KWARG = re.compile('^[^"\']+[^"\']+\s*=\s*.+', re.DOTALL) # 用于匹配关键词参数



def load_module_plugin__func():
    '''递归加载plugin_functions包及子级包下所有模块中的插件函数'''


    head, tail = os.path.split(__file__)
    package_father_path, package = os.path.split(head)
    head += '/plugin_functions'

    def load_funcs(dir_path):
        nonlocal package_father_path
        if not os.path.isdir(dir_path):
            return
        for name in os.listdir(dir_path):
            full_path = os.path.join(dir_path, name)
            if os.path.isdir(full_path):
                load_funcs(full_path)
            elif not name.startswith('_') and name.endswith('.py'):
                temp_path = full_path.replace(package_father_path, '')
                relative_path = temp_path.replace('\\', '/').lstrip('/').replace('/', '.')
                module = importlib.import_module(relative_path.rstrip('.py'))
                for name, item in vars(module).items():
                    if isinstance(item, types.FunctionType):
                        PUGIN_FUNC_MAP[name] = item

    load_funcs(head)
load_module_plugin__func()


class Utils:
    '''工具类'''

    @staticmethod
    def get_user_variable(variable, user_resource_dict):
        '''
        获取用户变量的值

        :param variable, 变量表达式，格式说明如下：
        # varName 获取变量名为varName的值
        # vaName[1] 获取变量名为varName的列表、元组变量值中，索引为1的元素值
        # varName.data 获取变量名为varName的字典变量值中，data属性值
        # 以上格式的组合 varName[1].data[0].statusCode (假设data是个列表

        '''

        src_variable_name = variable
        temp_list = variable.split('.')

        variable_express = ''
        temp_var_name = ''.join(temp_list[0:1]).strip()
        res = REG_FOR_ARRAY_ELEMENT.findall(temp_var_name)
        if res: # 获取的变量名称形如 data[1]
            variable = res[0]
            variable_express = temp_var_name[len(res[0]):]
        else:
            variable = temp_var_name

        # 获取用于对变量进一步取值的表达式
        for item in temp_list[1:]:
            item = item.strip()
            res = REG_FOR_ARRAY_ELEMENT.findall(item)

            if res: # 获取的item值形如 data[0][1],说明表达式中为 .data[0][1], 需要替换 .data[0][1] 为["data"][0][1]
                item = '["%s"]%s' % (res[0], item[len(res[0]):])
            else: #获取的item值形如 data ,说明表达式中为 .data, 需要替换 为["data"]
                item = '["%s"]' % item
            variable_express += item

        if variable in user_resource_dict:
            variable = user_resource_dict[variable]
            variable_express = 'variable'+ variable_express
            try:
                var_value = eval(variable_express)
                logger.debug('获取的变量值为：%s'% var_value)
                return var_value
            except Exception as e:
                logger.error('获取变量值出错：%s' % e)
                raise Exception('%s' % e)
        return src_variable_name


    @staticmethod
    def analyze_data(raw_data, user_resource_dict):
        '''
        数据解析，主要是替换变量为对应变量值，替换函数为对应函数返回值

        :param raw_data:
        :param :
        :return: 解析后的数据
        '''

        if isinstance(raw_data, dict):
            analyzed_data = {}
            for key, value in raw_data.items():
                analyzed_data[key] = Utils.analyze_data(value, user_resource_dict)
            return analyzed_data
        elif isinstance(raw_data, (list, tuple, set)):
            return [Utils.analyze_data(item, user_resource_dict) for item in raw_data]
        elif isinstance(raw_data, str):
            # 先替换函数，再替换剩余变量
            # logger.debug('正在替换字符串中的函数，字符串：%s' % raw_data)
            raw_data = Utils.replace_function(raw_data, user_resource_dict)
            # logger.debug('替换字符串中的函数成功')
            if isinstance(raw_data, str):
                return Utils.replace_user_variable(raw_data, user_resource_dict)
            return raw_data
        else:
            return raw_data



    @staticmethod
    def replace_user_variable(src_string, user_resource_dict):
        '''
        动态替换用户变量
        user_resource_dict 存储用户资源的字典
        '''
        try:
            variable_list = REG_FOR_VAR_EXPRESS.findall(src_string)

            #如果原始字符串仅有一个变量，且除了变量占位符，不包含其它字符，则用变量值替换整个原始字符串，
            #这样，当要替换原始字符串为整数时，可满足该需求
            if len(variable_list) == 1 and src_string == variable_list[0]:
                logger.info('查找到变量：%s' % variable_list[0])
                variable = variable_list[0].strip('${').strip('}').strip()
                src_string = Utils.get_user_variable(variable, user_resource_dict)
                logger.debug('正在替换变量 %s 为以下值：' % variable_list[0])
                logger.debug(src_string)
                return src_string


            for item in variable_list:
                # ##################################
                # variable对应的值可能是字符串、数字、Bool值 ，形如 “39849949393”，23, 4.5, True
                # 也可能是字典，列表、元组，集合 形如 {"id":1, "name":"tester"}， [1, 2, 3, 4]、[{"id":1, "name", "tester"}], [(1,3),("apple", "orange")]，(1， 2， 3， 4),
                # 还可能是其它
                # 变量名称希望支持的格式如下：
                # varName 获取该变量名为varName的变量值
                # varName.attribute 表示获取变量名为varName的字典变量的attribute属性值
                # varName[N] 表示获取变量名为varName的列表、元组变量值中，索引为的N的元素值
                # 支持组合 varName[1].id  varName[1][0].data[0].id
                # example:
                # varName.id 获取变量名为varName的字典变量的id属性值
                # varName[1] 获取变量名为varName的列表、元组中索引为1的元素值
                # ##################################
                logger.info('查找到变量：%s' % item)
                variable = item.strip('${').strip('}').strip()
                res = str(Utils.get_user_variable(variable, user_resource_dict))
                logger.debug('正在替换变量 %s 为 %s' % (item, res))
                src_string = src_string.replace(item, res)
        except Exception as e:
            logger.error('替换动态变量出错：%s' % e)
        finally:
            return src_string



    @classmethod
    def _replace_function(cls, string, user_resource_dict, func_map):
        '''替换字符串中的插件函数'''


        string = string.strip()
        func_name_list = REG_FOR_TEMP_PLUGIN_FUNC.findall(string) # 获取函数名称列表 形如 [''@plugin_func__base64@','@plugin_func__read_file@']
        if len(func_name_list) == 1 and string == '@%s@' % func_name_list[0]: # 整个字符串就是一个函数表达式，字符串代表的值的类型和函数返回值类型相同,如果函数不存在，返回None
            if func_name_list[0] in func_map:
                return cls.call_plugin_func(func_map.get(func_name_list[0]), user_resource_dict, func_map)
        else:
            for func_name in func_name_list:
                if func_name in func_map:
                    string = string.replace('@%s@' % func_name, str(cls.call_plugin_func(func_map.get(func_name), user_resource_dict, func_map)))
            return string


    @classmethod
    def call_plugin_func(cls, function_express, user_resource_dict, func_map):
        '''
        调用插件函数
        '''
        try:
            result = REG_FOR_FUNC_NAME_AND_ARGS.findall(function_express) # 查找函数表达式中的函数定义（函数名称及其参数）
            if result:
                plugin_func_name, plugin_func_args = result[0]
                plugin_func_name = plugin_func_name.strip('_') # 去掉函数前缀标识 _ 以获取真正的函数

                plugin_func_args = plugin_func_args.strip()
                plugin_func_arg_list = []
                if plugin_func_args:
                    plugin_func_arg_list = plugin_func_args.split("||") # 函数参数要求用 || 分隔

                position_arg_list = [] # 存放位置参数
                keyword_arg_dict = {}  # 存放关键词参数

                for item in plugin_func_arg_list:
                    item = item.strip()
                    if REG_FOR_KWARG.findall(item): # 关键词参数
                        key, value = re.split('\s*=[=|\s]*', item)
                        try:
                            value = cls._replace_function(value, user_resource_dict, func_map)
                            if isinstance(value, str):
                                value = cls.replace_user_variable(value, user_resource_dict)
                            keyword_arg_dict[key.strip()] = eval(value)
                        except Exception as e:
                            keyword_arg_dict[key.strip()] = value
                    else:
                        try:
                            value = cls._replace_function(item, user_resource_dict, func_map)
                            if isinstance(value, str):
                                value = cls.replace_user_variable(value, user_resource_dict)
                            position_arg_list.append(eval(value))
                        except Exception as e:
                            position_arg_list.append(value)
                if plugin_func_name in PUGIN_FUNC_MAP:
                    return PUGIN_FUNC_MAP.get(plugin_func_name)(*position_arg_list, **keyword_arg_dict)
                else:
                    return None
            else: #未找到函数
                print('没有找到同函数表达式( %s )匹配的函数定义' % function_express)
                # logger.warn('没有找到同函数表达式(%s)匹配的函数定义' % function_express)
                return None #
        except Exception as e:
            raise


    @classmethod
    def replace_function(cls, string, user_resource_dict, func_map={}):
        '''替换函数
        :params func_map  存放程序执行过程中，获取的临时函数名称和函数表达式的映射关系
        '''

        try:
            # 获取临时函数名称
            temp_func_name_list = REG_FOR_FUNC_NAME_OF_EXP.findall(string)
            string_copy = string

            old_func_name_set = set() # 存放上一次的查找结果
            while old_func_name_set != set(temp_func_name_list):
                old_func_name_set = set(temp_func_name_list)
                for func_name in temp_func_name_list: # 遍历查找函数对应的函数表达式
                    pattern = '\${\s*%s\(.*?\)\s*}' % func_name
                    func_express_list = re.findall(pattern, string_copy) # 获取函数表达式(因为可能存在函数嵌套，所以获取的表达式可能是错误的)

                    if not func_express_list: # 找不到函数表达式，说明该函数名称无效，不合法
                        continue

                    for func_express in func_express_list:
                        temp_func_express = func_express.strip().lstrip('${')
                        if not REG_FOR_STRICT_FUNC_EXP.findall(temp_func_express): # 表达式不包含嵌套函数，则  获取正确的函数表达式进行替换
                            right_func_express_list = REG_FOR_STRICT_FUNC_EXP.findall(func_express)
                            for right_func_express in right_func_express_list:
                                string_copy = string_copy.replace(right_func_express, '@plugin_func%s@' % func_name)
                                func_map['plugin_func%s' % func_name] = right_func_express # 建立临时函数名称和函数表达式的映射关系
                temp_func_name_list = REG_FOR_FUNC_NAME_OF_EXP.findall(string_copy)
            if string_copy == string: # 无变化
                return string
            return cls._replace_function(string_copy, user_resource_dict, func_map)
        except Exception as e:
            logger.error('替换函数出错%s' % e)
            return string


    @staticmethod
    def traverse_dir_files(dir_path, result=[]):
        '''递归遍历目录下的所有文件'''
        if not os.path.isdir(dir_path):
            return result
        for name in os.listdir(dir_path):
            full_path = os.path.join(dir_path, name)
            if os.path.isdir(full_path):
                Utils.traverse_dir_files(full_path)
            else:
                result.append(full_path)
        return result

    @staticmethod
    def get_scenario_config_files(dir_path):
        '''递归遍历获取指定目录下的场景配置文件'''

        result_list = []

        def get_config_files(dir_path, result_list):
            if not os.path.isdir(dir_path):
                return result_list
            for name in os.listdir(dir_path):
                full_path = os.path.join(dir_path, name)
                if os.path.isdir(full_path):
                    Utils.get_scenario_config_files(full_path)
                elif not name.startswith('_') and name.endswith('.py'):
                    result_list.append(full_path.replace('\\', '/'))
        get_config_files(dir_path, result_list)
        return result_list

